Skip to content

remove legacy tokenizer system - #1989

Merged
yaoyu-33 merged 8 commits into
mainfrom
dpykhtar/remove_legacy_tokenizers
Feb 11, 2026
Merged

remove legacy tokenizer system#1989
yaoyu-33 merged 8 commits into
mainfrom
dpykhtar/remove_legacy_tokenizers

Conversation

@dimapihtar

@dimapihtar dimapihtar commented Jan 19, 2026

Copy link
Copy Markdown
Contributor

What does this PR do ?

  1. Fully removes legacy tokenizers mplemenation in favor to the new tokenizers. Legacy tokenizers were also removed from MCore so we're unable to support them in MBridge as well.
  2. Updates MBridge to latest MCore.

Changelog

  • Add specific line by line info of high level changes in this PR.

GitHub Actions CI

See the CI sectionin the Contributing doc for how to trigger the CI. A Nvidia developer will need to approve and trigger the CI for external contributors.

Before your PR is "Ready for review"

Pre checks:

  • Make sure you read and followed Contributor guidelines
  • Did you write any new necessary tests?
  • Did you add or update any necessary documentation?
  • Does the PR affect components that are optional to install? (Ex: Numba, Pynini, Apex etc)
    • Reviewer: Does the PR have correct import guards for all optional libraries?

If you haven't finished some of the above items you can still open "Draft" PR.

Additional Information

  • Related to # (issue)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for SFTTokenizer and NullMultimodalTokenizer options in tokenizer configuration.
    • Added system message forcing configuration option.
  • Refactor

    • Consolidated tokenizer initialization into a unified factory function.
    • Removed legacy tokenizer implementations (BERT, GPT-2) and multimodal tokenizer module.
    • Simplified tokenizer configuration by removing deprecated flags.
  • Tests

    • Updated tokenizer tests to align with refactored system; removed legacy test coverage.

Signed-off-by: dimapihtar <dpihtar@gmail.com>
@copy-pr-bot

copy-pr-bot Bot commented Jan 19, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@dimapihtar
dimapihtar marked this pull request as draft January 20, 2026 11:48
dimapihtar and others added 2 commits January 20, 2026 06:39
@dimapihtar
dimapihtar marked this pull request as ready for review February 9, 2026 16:49
@dimapihtar

Copy link
Copy Markdown
Contributor Author

/ok to test d98abbc

@coderabbitai

coderabbitai Bot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR consolidates the tokenizer infrastructure by removing legacy tokenizer implementations (BERT, GPT2 BPE, multimodal) and their factory utilities, while introducing a unified build_tokenizer() factory function in the main tokenizer module. Configuration is simplified by removing the legacy_tokenizer flag and adding support for new tokenizer types (NullMultimodalTokenizer, SFTTokenizer).

Changes

Cohort / File(s) Summary
Legacy tokenizer implementations removal
src/megatron/bridge/training/tokenizers/bert_tokenization.py, gpt2_tokenization.py, multimodal_tokenizer.py, src/megatron/bridge/training/tokenizers/utils.py
Entirely removed legacy BERT, GPT2 BPE, and multimodal tokenizer implementations, plus the utils factory module (1,135 lines deleted). Tokenization now depends on external implementations.
Tokenizer configuration and factory refactoring
src/megatron/bridge/training/tokenizers/config.py, tokenizer.py
Introduced new unified build_tokenizer() factory function to replace legacy per-tokenizer classes. Removed legacy_tokenizer config flag, added force_system_message option, extended tokenizer_type support (NullMultimodalTokenizer, SFTTokenizer). Deprecated internal classes like MegatronLegacyTokenizer and _HuggingFaceTokenizer in favor of config-driven dispatch.
Dataset builder simplification
src/megatron/bridge/data/builders/finetuning_dataset.py
Removed legacy tokenizer compatibility branch; now always treats tokenizer as HuggingFaceTokenizer and derives model name exclusively from path without conditional branching.
Test suite migration
tests/unit_tests/training/test_tokenizer.py, test_chat_template.py
Updated imports from utils to tokenizer module, replaced build_new_tokenizer with build_tokenizer, added new test for local HF tokenizer loading, removed space-sensitivity test cases.
Test suite removal
tests/unit_tests/training/test_tokenizer_legacy.py
Entirely removed legacy tokenizer test file (545 lines) covering deprecated implementations and edge cases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • yaoyu-33
  • ko3n1g
  • cuichenx
🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Results For Major Changes ⚠️ Warning Major refactoring removing legacy tokenizer system (800+ lines, multiple breaking API changes) lacks documented test results or regression validation in PR description. Update PR description with test results, regression validation, convergence testing, detailed changelog, and address existing code review comments before review.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'remove legacy tokenizer system' accurately summarizes the main change in the PR, which involves eliminating legacy tokenizer classes, functions, and modules throughout the codebase.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dpykhtar/remove_legacy_tokenizers

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/megatron/bridge/training/tokenizers/tokenizer.py`:
- Around line 47-57: The TikTokenizer branch sets kwargs["vocab_size"] twice
which risks overwriting a valid value with None; in the block where
config.tokenizer_type == "TikTokenizer" remove the duplicate unconditional
assignment of kwargs["vocab_size"] and only set kwargs["vocab_size"] when
config.vocab_size is not None (i.e., keep the existing guarded assignment if
config.vocab_size is present and do not reassign later). Target the tokenizer.py
TikTokenizer branch and the kwargs dict modifications (keys "vocab_size",
"num_special_tokens", "special_tokens", "pattern", "chat_template").
- Around line 15-27: The function build_tokenizer currently clobbers
caller-supplied kwargs by doing kwargs = {}, so change it to preserve incoming
kwargs and merge or update it with config-derived values instead of resetting;
locate the build_tokenizer function and remove the kwargs = {} assignment, then
populate tokenizer_library and any config-derived options by updating kwargs
(e.g., kwargs.update({...}) or creating a new dict merged = {**kwargs,
**config_values}) before passing them into MegatronTokenizer initialization to
ensure caller kwargs are honored.

In `@tests/unit_tests/training/test_tokenizer.py`:
- Around line 125-168: Add the `@pytest.mark.unit` marker to
test_hf_tokenizer_as_local_path_object and remove the network call to
AutoTokenizer.from_pretrained; instead create a tiny local tokenizer with the
tokenizers library, save it to tmp_path, and use that path in the existing
TokenizerConfig so build_tokenizer loads from disk. Specifically, replace the
AutoTokenizer.from_pretrained("bert-base-uncased") step with constructing a
small Tokenizer (e.g., a WordLevel or BPE model), train/populate it with a tiny
vocabulary or trainer, call tokenizer.save_pretrained or the tokenizers
equivalent to write tokenizer files into local_model_path, then proceed with the
existing TokenizerConfig(tokenizer_model=local_model_path) and assertions; keep
assertions comparing build_tokenizer output to the locally saved tokenizer and
verifying files exist.

Comment on lines +15 to +27
def build_tokenizer(config: TokenizerConfig, **kwargs) -> MegatronTokenizer:
"""Initialize tokenizer from megatron.core.tokenizers based on the provided configuration.

Args:
tokenizer_instance: Tokenizer instance with a `tokenize` method
default: Fallback value if computation fails (True for SentencePiece, False for others)

Returns:
bool: True if the tokenizer is space-sensitive, False otherwise

Example:
# A space-sensitive tokenizer (e.g., many BPE tokenizers):
# tokenize("x y") -> [87, 331]
# tokenize("x") + tokenize("y") -> [87, 379] # Different!

# A non-space-sensitive tokenizer would produce the same result
"""
try:
test_tokens_with_space = tokenizer_instance.tokenize("x y")
test_tokens_concat = tokenizer_instance.tokenize("x") + tokenizer_instance.tokenize("y")
return test_tokens_with_space != test_tokens_concat
except Exception:
# If tokenization fails for any reason, use the default
return default


class MegatronLegacyTokenizer(MegatronTokenizerCore):
"""Base tokenizer class, extending the MegatronTokenizer from megatron core.

This class provides a common interface for various tokenizers used within the NeMo framework.
"""

def __init__(self, *args, **kwargs):
# Set legacy attribute
self.legacy = True
super().__init__(*args, **kwargs)

def __call__(self, *args, **kwargs):
"""Makes the tokenizer instance callable, synonym for `tokenize`."""
return self.tokenize(*args, **kwargs)

def text_to_ids(self, text: str) -> list[int]:
"""Converts text to a list of token IDs."""
return self.tokenize(text)

@property
def eod_id(self):
"""ID for the end-of-document token."""
return self.eod

@property
def bos_id(self):
"""ID for the beginning-of-sentence token."""
return self.bos

@property
def eos_id(self):
"""ID for the end-of-sentence token."""
return self.eos

@property
def mask_id(self):
"""ID for the mask token."""
return self.mask


def build_tokenizer(tokenizer_config: TokenizerConfig, **kwargs) -> MegatronLegacyTokenizer | MegatronTokenizer:
"""Initialize tokenizer based on the provided configuration.

This function serves as a factory to instantiate various tokenizer types
supported by NeMo Framework, such as BERT, GPT2, SentencePiece, HuggingFace, etc.
It also handles padding the vocabulary size to be GPU-friendly.

Args:
tokenizer_config (TokenizerConfig): Configuration object specifying the tokenizer
config (TokenizerConfig): Configuration object specifying the tokenizer
type, paths to vocab/model files, and other
tokenizer-specific settings.
**kwargs: Additional keyword arguments that might be specific to certain tokenizers
(e.g., passed to HuggingFace AutoTokenizer). These will override any
kwargs specified in tokenizer_config.hf_tokenizer_kwargs.

Returns:
MegatronTokenizer: An instance of the initialized tokenizer.

Raises:
NotImplementedError: If the specified tokenizer_type in tokenizer_config is not supported.
ImportError: If a required library (e.g., transformers for MultimodalTokenizer) is not installed.
"""
if get_rank_safe() == 0:
print("> building {} tokenizer ...".format(tokenizer_config.tokenizer_type), flush=True)
kwargs = {}
tokenizer_library = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t discard caller‑supplied kwargs.

kwargs = {} drops any kwargs passed into build_tokenizer, which can silently break callers. Preserve them and layer config‑derived values on top.

Suggested fix
-    kwargs = {}
+    tokenizer_kwargs = dict(kwargs)
@@
-        kwargs["additional_special_tokens"] = config.special_tokens if config.special_tokens else []
+        tokenizer_kwargs["additional_special_tokens"] = config.special_tokens if config.special_tokens else []
@@
-            special_tokens = {}
-            special_tokens["additional_special_tokens"] = [f"<extra_id_{i}>" for i in range(100)]
-            kwargs = special_tokens
+            tokenizer_kwargs["additional_special_tokens"] = [f"<extra_id_{i}>" for i in range(100)]
@@
-        kwargs["vocab_file"] = config.vocab_file
-        kwargs["merges_file"] = config.merge_file
+        tokenizer_kwargs["vocab_file"] = config.vocab_file
+        tokenizer_kwargs["merges_file"] = config.merge_file
@@
-            kwargs.update(config.hf_tokenizer_kwargs)
+            tokenizer_kwargs.update(config.hf_tokenizer_kwargs)
@@
-    tokenizer = MegatronTokenizer.from_pretrained(tokenizer_path=tokenizer_path, metadata_path=metadata, **kwargs)
+    tokenizer = MegatronTokenizer.from_pretrained(
+        tokenizer_path=tokenizer_path,
+        metadata_path=metadata,
+        **tokenizer_kwargs,
+    )
🤖 Prompt for AI Agents
In `@src/megatron/bridge/training/tokenizers/tokenizer.py` around lines 15 - 27,
The function build_tokenizer currently clobbers caller-supplied kwargs by doing
kwargs = {}, so change it to preserve incoming kwargs and merge or update it
with config-derived values instead of resetting; locate the build_tokenizer
function and remove the kwargs = {} assignment, then populate tokenizer_library
and any config-derived options by updating kwargs (e.g., kwargs.update({...}) or
creating a new dict merged = {**kwargs, **config_values}) before passing them
into MegatronTokenizer initialization to ensure caller kwargs are honored.

Comment on lines +47 to +57
elif config.tokenizer_type == "TikTokenizer":
tokenizer_library = "tiktoken"
tokenizer_path = config.tokenizer_model
kwargs["chat_template"] = config.chat_template
if config.tiktoken_pattern:
kwargs["pattern"] = config.tiktoken_pattern
if config.vocab_size:
kwargs["vocab_size"] = config.vocab_size
kwargs["num_special_tokens"] = config.tiktoken_num_special_tokens
kwargs["special_tokens"] = config.special_tokens
kwargs["vocab_size"] = config.vocab_size

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid overwriting TikTokenizer vocab_size with None.

vocab_size is assigned twice; the unconditional assignment can pass None and override a prior conditional value. Remove the duplicate and guard with is not None.

Suggested fix
-        if config.vocab_size:
-            kwargs["vocab_size"] = config.vocab_size
+        if config.vocab_size is not None:
+            kwargs["vocab_size"] = config.vocab_size
@@
-        kwargs["vocab_size"] = config.vocab_size
🤖 Prompt for AI Agents
In `@src/megatron/bridge/training/tokenizers/tokenizer.py` around lines 47 - 57,
The TikTokenizer branch sets kwargs["vocab_size"] twice which risks overwriting
a valid value with None; in the block where config.tokenizer_type ==
"TikTokenizer" remove the duplicate unconditional assignment of
kwargs["vocab_size"] and only set kwargs["vocab_size"] when config.vocab_size is
not None (i.e., keep the existing guarded assignment if config.vocab_size is
present and do not reassign later). Target the tokenizer.py TikTokenizer branch
and the kwargs dict modifications (keys "vocab_size", "num_special_tokens",
"special_tokens", "pattern", "chat_template").

Comment on lines +125 to +168
@pytest.mark.timeout(30)
def test_hf_tokenizer_as_local_path_object(self, tmp_path):
# Cover the user case where a user has made a local path object of a WIP tokenizer and wants
# to use that in some megatron model at train time.

# First as a proxy download a tokenizer from HF and save it to a local path. A user would
# do this differently by exporting their WIP tokenizer to a local path.

# 1. Download a common, small tokenizer from the Hub
# "bert-base-uncased" is a safe choice as it's small and standard.
model_id = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_id)

# 2. Define a local path in the temporary directory
local_model_path = tmp_path / "my_local_tokenizer"

# 3. Save the tokenizer to disk
# This creates tokenizer_config.json, vocab.txt, special_tokens_map.json, etc.
tokenizer.save_pretrained(str(local_model_path))

# 4. Load it back using the local path
# This simulates the user providing a path to a folder instead of a Hub ID
cfg = TokenizerConfig(
tokenizer_type="HuggingFaceTokenizer",
tokenizer_model=local_model_path,
hf_tokenizer_kwargs={
"trust_remote_code": True,
"include_special_tokens": True,
},
)
loaded_tokenizer = build_tokenizer(cfg)

# 5. Verify it functions identically
test_text = "Unit testing is important."

original_tokens = tokenizer.encode(test_text)
reloaded_tokens = loaded_tokenizer.tokenize(test_text)

assert original_tokens == reloaded_tokens
assert loaded_tokenizer.vocab_size == tokenizer.vocab_size

# verify that the directory actually contains files (sanity check)
assert (local_model_path / "tokenizer_config.json").exists()
assert (local_model_path / "tokenizer.json").exists()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the test file
fd "test_tokenizer.py" tests/unit_tests/

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 114


🏁 Script executed:

# Check the actual content of the test file at the specified lines
cat -n tests/unit_tests/training/test_tokenizer.py | sed -n '115,180p'

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 2745


🏁 Script executed:

# Check the imports at the top of the file to understand what's available
head -50 tests/unit_tests/training/test_tokenizer.py

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 1881


🌐 Web query:

AutoTokenizer.from_pretrained network dependency HuggingFace transformers

💡 Result:

AutoTokenizer.from_pretrained(...) depends on network access only when the required tokenizer files aren’t already available locally. By default, it will try to fetch missing files from the Hugging Face Hub and cache them under ~/.cache/huggingface/hub (unless you change the cache location). [1]

How to avoid any network calls:

  • Force offline (no HTTP to the Hub):
    • Set HF_HUB_OFFLINE=1. Then from_pretrained() will only use locally cached files and fail if they’re missing. [2]
  • Per-call: only load local files:
    • AutoTokenizer.from_pretrained("...or local path...", local_files_only=True) [2]
  • Pre-download for offline use, then load from disk:
    • Use huggingface_hub.snapshot_download(...) ahead of time, then point from_pretrained() at the downloaded directory. [1]

Cache location / controlling where files are stored:

  • Hub downloads (models/tokenizers) default to ~/.cache/huggingface/hub and can be redirected with HF_HUB_CACHE, or more broadly with HF_HOME. [3]

Sources:
[1] Transformers installation docs (cache setup + snapshot_download) https://huggingface.co/docs/transformers/v4.52.2/installation
[2] Transformers installation docs (offline mode + local_files_only) https://huggingface.co/docs/transformers/v4.49.0/installation
[3] Hugging Face docs (hub cache location + HF_HOME / HF_HUB_CACHE) https://huggingface.co/docs/datasets/main/en/cache


🌐 Web query:

PreTrainedTokenizerFast save_pretrained files created tokenizer_config

💡 Result:

PreTrainedTokenizerFast.save_pretrained(save_dir) always writes a tokenizer_config.json because Transformers needs a persisted “init kwargs” bundle to be able to reconstruct the tokenizer with from_pretrained().

What gets saved (typical cases):

  • Always (both fast & slow tokenizers):

    • tokenizer_config.json — a JSON dump of self.init_kwargs (plus init_inputs when present) and an added tokenizer_class field; it is written unconditionally by save_pretrained(). [1]
    • special_tokens_map.json — serialized special-token settings. [1]
  • Fast tokenizers (tokenizer.is_fast == True):

    • tokenizer.json — the “full tokenizer” single-file format (FULL_TOKENIZER_FILE = "tokenizer.json"). [1]
  • Slow/legacy tokenizers (or when saved in legacy format):

    • Model-specific vocab files (e.g., vocab.json, merges.txt, vocab.txt, etc.) and possibly added_tokens.json when there are added tokens. [1]

Notes that often confuse people:

  • tokenizer_config.json commonly contains name_or_path because the base tokenizer stores it from the from_pretrained(...) argument (init_kwargs["name_or_path"] = pretrained_model_name_or_path). [2]
  • If you expected tokenizer.json but didn’t get it, it usually means you’re not saving/loading a fast tokenizer; confirm with tokenizer.is_fast. [3]

Sources: [1] [2] [3]


Add @pytest.mark.unit marker and make test network-independent by building a tiny local tokenizer.

The test currently calls AutoTokenizer.from_pretrained("bert-base-uncased"), which hits the Hugging Face Hub network on the first run and violates unit test isolation requirements. Build a small in-memory tokenizer using the tokenizers library and save it locally instead. Additionally, add the @pytest.mark.unit marker per test categorization guidelines.

Suggested implementation
-    `@pytest.mark.timeout`(30)
+    `@pytest.mark.unit`
+    `@pytest.mark.timeout`(30)
     def test_hf_tokenizer_as_local_path_object(self, tmp_path):
         # Cover the user case where a user has made a local path object of a WIP tokenizer and wants
         #  to use that in some megatron model at train time.
 
         # First as a proxy download a tokenizer from HF and save it to a local path. A user would
         #  do this differently by exporting their WIP tokenizer to a local path.
 
-        # 1. Download a common, small tokenizer from the Hub
-        # "bert-base-uncased" is a safe choice as it's small and standard.
-        model_id = "bert-base-uncased"
-        tokenizer = AutoTokenizer.from_pretrained(model_id)
+        # 1. Build a tiny local tokenizer to avoid network calls.
+        from tokenizers import Tokenizer, models, pre_tokenizers, trainers
+        from transformers import PreTrainedTokenizerFast
+
+        raw_tokenizer = Tokenizer(models.WordLevel(unk_token="[UNK]"))
+        raw_tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()
+        trainer = trainers.WordLevelTrainer(special_tokens=["[UNK]", "[PAD]"])
+        raw_tokenizer.train_from_iterator(["hello world"], trainer)
+        tokenizer = PreTrainedTokenizerFast(
+            tokenizer_object=raw_tokenizer,
+            unk_token="[UNK]",
+            pad_token="[PAD]",
+        )
🤖 Prompt for AI Agents
In `@tests/unit_tests/training/test_tokenizer.py` around lines 125 - 168, Add the
`@pytest.mark.unit` marker to test_hf_tokenizer_as_local_path_object and remove
the network call to AutoTokenizer.from_pretrained; instead create a tiny local
tokenizer with the tokenizers library, save it to tmp_path, and use that path in
the existing TokenizerConfig so build_tokenizer loads from disk. Specifically,
replace the AutoTokenizer.from_pretrained("bert-base-uncased") step with
constructing a small Tokenizer (e.g., a WordLevel or BPE model), train/populate
it with a tiny vocabulary or trainer, call tokenizer.save_pretrained or the
tokenizers equivalent to write tokenizer files into local_model_path, then
proceed with the existing TokenizerConfig(tokenizer_model=local_model_path) and
assertions; keep assertions comparing build_tokenizer output to the locally
saved tokenizer and verifying files exist.

@dimapihtar

Copy link
Copy Markdown
Contributor Author

/ok to test dfcba18

Signed-off-by: dimapihtar <dpihtar@gmail.com>
@dimapihtar

Copy link
Copy Markdown
Contributor Author

/ok to test 61df1ee

Signed-off-by: dimapihtar <dpihtar@gmail.com>
Signed-off-by: dimapihtar <dpihtar@gmail.com>
@dimapihtar

Copy link
Copy Markdown
Contributor Author

/ok to test 3b57148

@dimapihtar
dimapihtar requested a review from yaoyu-33 February 11, 2026 14:53

@ko3n1g ko3n1g left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tested internally, good to merge

@yaoyu-33
yaoyu-33 merged commit 7414c9b into main Feb 11, 2026
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants